require 'thread'
require 'socket'
require 'timeout'
require 'set'
require './nodes'
require './messages'
require './threadout'

Thread::abort_on_exception = true

class DistributedNode < Node
  attr_reader :nodes
  attr_accessor :requests

  def initialize id 
    @nodes = NodeManager.new
    @nodes.load 'config.yml'

    super id.to_i, '0.0.0.0', @nodes.get(id).port

    @message_queue = Queue.new

    @server = Server.new self.port, self
    @client = Client.new self, @nodes
    @watchdog = WatchDog.new self, @nodes

    @requests = Queue.new
  end

  def run
    @server_thread = Thread.new { @server.run }
    @watchdog_thread = Thread.new { @watchdog.run }
    @client.send_all :HELLO
    loop do
      handle_input
    end
  end

  def handle_input
    print "#{@server_thread.alive? ? "ON" : "OFF" } #{@state.has_critical ? "\u2295 " : ''}> "
    cmd = STDIN.gets.strip
    case cmd
    when 'n', 'nodes'   then puts @nodes
    when 't', 'take'    then take
    when 'r', 'rel'     then release
    when 'on'           then server :on
    when 'off'          then server :off
    when 'l', 'list'    then puts Thread.list
    when 'c', 'clock'   then puts "Clock => #{@clock}"
    when 'p', 'pend'    then @nodes.each { |n| puts n.pending }
    when 'q', 'quit'    then exit
    else @client.send_all cmd unless cmd == ''
    end
  end

  def handle message
    part = message.split ':'
    type = Message::reverse part[0]
    id = part[1].to_i
    clock = part[2].to_i

    puts "Handling message => #{message}"
    puts "Type #{type}" if type

    case type
    when :HELLO   then handle_hello id
    when :REQUEST then handle_request id, clock
    when :GRANT   then handle_grant id
    when :ZOMBIE  then handle_zombie id
    end

    # update clock
    if clock >= self.clock && [:REQUEST, :GRANT, :HELLO, :OK].include?(type)
      puts "Updated clock from #{self.clock} to #{clock + 1}" 
      self.clock = clock + 1
    end
    @client.send_messge @nodes.get(id), :OK unless type == :OK || type == :PING
  end

  def handle_hello id
    node = @nodes.get(id)
    node.zombie = false
    #@client.send_pending node
  end

  def handle_request id, clock
    node = @nodes.get id
    return if node == self
    if !@state.has_critical && !@state.critical_requested
      @client.send_messge node, :GRANT
    elsif @state.critical_requested && !@state.has_critical && clock < @clock
      @client.send_messge node, :GRANT
    elsif @state.critical_requested && !@state.has_critical && clock == @clock && id < @id
      @client.send_messge node, :GRANT
    else
      @requests << node
    end
  end

  def handle_grant id
    puts "Handling grant..."
    if @state.critical_requested
      @state.granted << id

      puts @state.granted.inspect
      puts @nodes.ids.inspect

      # check if all have granted and self is not the only node
      if @state.granted == @nodes.ids && @nodes.ids.size != 1 
        puts 'Has CRITICAL now'
        @state.has_critical = true
      end
    end
  end

  def handle_zombie id
    if self.id.to_s != id.to_s
      node = @nodes.get id
      node.zombie = true
    else
      puts "I'm a zombie?!"
      @client.send_all :HELLO 
      release
    end
  end

  def take
    @client.send_all :REQUEST
    @state.granted << self.id
    @state.critical_requested = true
  end

  def release
    puts "Releasing..."
    @state.reset
    while not @requests.empty?
      node = @requests.pop
      puts "Notifing node #{node}"
      @client.send_messge node, :GRANT
    end 
  end

  def server state
    if state == :off && @server_thread.alive?
      @server_thread.exit
      sleep 0.1 while @server_thread.alive?
      @server.close
    elsif state == :on && !@server_thread.alive?
      @server_thread = Thread.new { @server.run }
    end
  end
end

class Server
  def initialize port, dispatcher
    @port = port
    @nodes = dispatcher.nodes
    @dispatcher = dispatcher
  end

  def run
    puts "Starting server on #{@port}"
    @server = TCPServer.new @port
    loop do
      Thread.start(@server.accept) do |client|
        message = client.read
        @dispatcher.handle message
        client.close
      end
    end
  end

  def close
    @server.close if @server
  end
end

class Client
  def initialize self_node, nodes
    @self_node = self_node
    @nodes = nodes
    puts "Self is ID#{self_node.id}"
  end

  def send_messge node, msg
    send node, [Message.const_get(msg.to_sym), @self_node.id, @self_node.clock].join(':')
  end

  def send node, message
    begin
      puts "Sending #{message} to #{node}"
      socket = TCPSocket.open node.ip, node.port
      timeout(TIMEOUT) do
        socket.print  message
      end
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error
      unless node.zombie
        puts "ID#{node.id} is zombie"
        node.zombie = true
        signal_zombie node.id
      end

      type, id, c = Message::unpack message
      # add to pending if not zombie
      if type != :ZOMBIE && node.id != id
        node.pending << message
      end
    else
      if node.zombie
        puts "ID#{node.id} connected"
        node.zombie = false
        # resend all pending messages
        #send_pending node
      end
    ensure 
      socket.close if socket
    end
  end

  def send_all message, id = @self_node.id
    @nodes.each { |node| send_messge node, message unless node == @self_node }
  end

  def send_to nodes, message, id = @self_node.id
    nodes.each { |node| send_messge node, message }
  end

  def signal_zombie id
    @nodes.active.each { |n| send n, Message.pack(:ZOMBIE, id, @self_node.clock) }
  end

  def send_pending node
    while not node.pending.empty?
      send node, node.pending.pop
    end
  end
end

class WatchDog < Client
  def initialize self_node, nodes
    @self_node = self_node
    @nodes = nodes
  end

  def run
    #Thread.current[:stdout] = StringIO.new
    loop do
      @nodes.zombies.each { |z| send z, Message.pack(:ZOMBIE, z.id, @self_node.clock) }
      sleep TIMEOUT
    end
  end
end
